iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
自我挑戰組

React 開發者的 TypeScript 探索之旅系列 第 21 篇

【 Day 21 】在 TypeScript 中使用 setState 進行狀態管理

  • 分享至 

  • xImage
  •  

本系列文章 GitHub

目前的 Todo List 在輸入欄位為空白時仍然可以送出。今天,我們要來修正這個問題,並為其新增錯誤提示訊息。此外,為了練習我們在 Day08 提到的 Literal Types,我們將在 Todo 成功建立時顯示成功提示訊息。


定義提示訊息型別與狀態

首先,在 components 資料夾中加入檔案 Message.tsx,並建立 Message 元件。Message 元件將放置在 App.tsx 中,並接收來自 App 的 props,透過這些 props 來控制顯示與否以及顯示的內容。因此,我們將 type 定義在 App.tsx 並匯出,以便 Message 元件可以共用這個型別。

由於我們的提示訊息只會有「成功」和「失敗」兩種狀態,因此可以使用 Literal Types 來定義 mode 的型別:

export type MessageDetails = {
  visible: boolean
  message: string
  mode: 'error' | 'success'
}

接著,在 App.tsx 中建立狀態,並定義其初始值:

const [MessageDetails, setMessageDetails] = useState<MessageDetails>({
  visible: false,
  message: '',
  mode: 'error',
})

由於 Message 元件會使用 fixed 定位,它可以放置在 <main> 中的任意位置。這裡我們選擇將它放置在頁面底部,並傳入定義好的狀態作為 props:

...
  <main className='w-[500px] h-[100dvh] portrait:w-[90%] flex flex-col'>
    <Header image={{ src: logo, alt: 'logo' }}>
      <h1>Todo List</h1>
    </Header>
    <CreateTodo onCreateTodo={createTodoHandler} />
    <TodoList todos={todos} onDeleteTodo={deleteTodoHandler} />
    <Message
      visible={messageDetails.visible}
      mode={messageDetails.mode}
      message={messageDetails.message}
    />
  </main>
...

建立 Message 元件

前往 Message.tsx 並匯入我們在 App.tsx 中定義的 MessageDetails 型別:

import { type MessageDetails } from '../App'

export default function Message({ visible, message, mode }: MessageDetails) {
  return (
    <div
      className={`${mode === 'error' ? 'bg-red-500' : 'bg-green-500'} ${
        visible ? 'flex' : 'hidden'
      } rounded-[5px] p-[10px] fixed bottom-[20px] left-[20px]`}
    >
      <p className='text-[20px]'>{message}</p>
    </div>
  )
}

打開瀏覽器,試著手動更改 App 元件中 messageDetails 的初始值,你應該可以看到像下方截圖中的這樣畫面:

https://ithelp.ithome.com.tw/upload/images/20241001/20169025u6MAcEEJpO.png


更新 Todo 新增邏輯

接著,回到 App 元件,在 createTodoHandler 函式中新增提示訊息的相關邏輯,以處理成功和錯誤狀態:

const createTodoHandler = (title: string) => {
  if (title.trim().length === 0) {
    setMessageDetails({
      visible: true,
      message: 'Input cannot be empty!',
      mode: 'error',
    })
    return
  }
  const newTodo: TodoItem = {
    id: Math.random(),
    title: title,
    isFinished: false,
  }
  setTodos((prevTodos) => [...prevTodos, newTodo])
  setMessageDetails({
    visible: true,
    message: 'Todo created successfully!',
    mode: 'success',
  })
}

處理訊息的顯示與隱藏

目前提示訊息的顯示功能已經完成,接下來,我們要處理提示訊息的自動隱藏。Message 元件的顯示狀態儲存在 App.tsx 中的 messageDetails 狀態,因此,我們需要將更新狀態的 setMessageDetails 函式傳遞給 Message 元件:

<Message
  visible={messageDetails.visible}
  mode={messageDetails.mode}
  message={messageDetails.message}
  onMessageVisible={setMessageDetails}
/>

在 Message 元件中,我們會使用先前在 Day08 提到的合併型別方法加入 onMessageVisible。為了增強程式碼的可讀性,這裡我們將型別定義獨立提出來,並設置 onMessageVisible 的參數型別為 SetStateAction,泛型參數則使用我們先前定義的 MessageDetails:

import { type SetStateAction } from 'react'

type MessageProps = MessageDetails & {
  onMessageVisible: (value: SetStateAction<MessageDetails>) => void
}

最後,加入 useEffect,當 visible 為 true 時,觸發計時器,在三秒後自動隱藏提示訊息,並清除上一次的計時器,避免計時器累加造成記憶體的負擔:

useEffect(() => {
  if (visible) {
    const timer = setTimeout(() => {
      onMessageVisible((prev: MessageDetails) => ({
        ...prev,
        visible: false,
      }))
    }, 3000)

    return () => clearTimeout(timer) 
  }
}, [visible])

【 補充 】Literal Types 的彈性應用與選項處理

在最一開始,我們提到了使用 Literal Types 定義型別:

export type MessageDetails = {
  visible: boolean
  message: string
  mode: 'error' | 'success'
}

若有一個元件不需要 mode,而我們在使用 MessageDetails 型別時沒有傳遞 mode 給該元件,那麼 TypeScript 會報錯。在這種情況下,我們可以有兩種解決方法。

第一種寫法是將 mode 設為可選的,並允許其值為 undefined,這樣元件即使沒有傳遞 mode 也不會報錯:

export type MessageDetails = {
  visible: boolean
  message: string
  mode: 'error' | 'success' | undefined
}

第二種寫法是在 mode 後加上 ?,這樣就會讓 mode 成為可選的屬性:

export type MessageDetails = {
  visible: boolean
  message: string
  mode?: 'error' | 'success'
}

兩種寫法效果相同,可以根據個人喜好選擇。


上一篇
【 Day 20 】useRef with TypeScript
下一篇
【 Day 22 】使用 useContext、useReducer 優化資料管理(一)
系列文
React 開發者的 TypeScript 探索之旅 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言